package ru.ifmo.croak.impl.storage.team1;

import com.sun.istack.internal.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.ifmo.croak.api.Message;

import java.sql.SQLException;
import java.util.AbstractList;
import java.util.Collections;
import java.util.List;

/**
 * @author Alex Vikharev
 *         22.05.11
 */
public class BatchList extends AbstractList<Message> {
    private final Logger log = LoggerFactory.getLogger(BatchList.class);
    private static final int MAX_BATCH_SIZE = 10000;

    private final DatabaseBatchQuery query;
    private Cache firstLevel;
    private Cache secondLevel;

    public BatchList(DatabaseBatchQuery query) {
        this.query = query;
        firstLevel = new Cache(Collections.<Message>emptyList(), 0);
        secondLevel = new Cache(Collections.<Message>emptyList(), 0);
    }

    @Override
    public Message get(final int index) {
        if (index < 0 || index >= size()) {
            throw new IndexOutOfBoundsException();
        }
        Message message = firstLevel.get(index);
        if (message != null) {
            return message;
        }
        message = secondLevel.get(index);
        if (message != null) {
            return message;
        }
        secondLevel = firstLevel;
        Cache temp = updateCache(index);
        message = temp.get(index);
        firstLevel = temp;
        return message;
    }

    private Cache updateCache(final int index) {
        int leftBound;
        int rightBound;
        if (index < secondLevel.offset) {
            rightBound = Math.min(secondLevel.offset, index + MAX_BATCH_SIZE / 2);
            leftBound = Math.max(0, rightBound - MAX_BATCH_SIZE);
            rightBound = Math.min(secondLevel.offset, leftBound + MAX_BATCH_SIZE);
        } else {
            leftBound = Math.max(secondLevel.offset + secondLevel.size, index - MAX_BATCH_SIZE / 2);
            rightBound = Math.min(size(), leftBound + MAX_BATCH_SIZE);
            leftBound = Math.max(secondLevel.offset + secondLevel.size, rightBound - MAX_BATCH_SIZE);
        }
        try {
            return new Cache(query.getBatch(rightBound - leftBound, leftBound), leftBound);
        } catch (SQLException e) {
            log.error("Can't get data from database", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public int size() {
        return query.getSize();
    }

    private class Cache {
        private final List<Message> data;
        private final int size;
        private final int offset;

        Cache(final List<Message> data, final int offset) {
            this.data = data;
            this.offset = offset;
            this.size = data.size();
        }

        @Nullable
        public Message get(int index) {
            if (index < offset || index - offset >= size) {
                return null;
            } else {
                return data.get(index - offset);
            }
        }

    }
}
